﻿using System.Collections.Generic;
using System.Linq;
using RoboContainer.Core;
using RoboContainer.Infection;
using TimeMachine.Engine.Vcs;

namespace TimeMachine.Engine.Metrics.Impl
{
	[MetricDescriptor("InternalRate", AnalyseStranges = true, ShowPersonalMetrics = true)]
	public class InternalRateMetric : BaseMetric
	{
		private readonly IChangesProcessor changesProcessor;
		private readonly ICommitsInfoProvider commitsProvider;
		private readonly Factory<DetailedCommitInfo, InternalRateMetricClaculator> createCalculator;

		public InternalRateMetric(
			IChangesProcessor changesProcessor,
			ICommitsInfoProvider commitsProvider,
			Factory<DetailedCommitInfo, InternalRateMetricClaculator> createCalculator)
		{
			this.changesProcessor = changesProcessor;
			this.commitsProvider = commitsProvider;
			this.createCalculator = createCalculator;
		}

		public override MetricValue Calculate(int rev)
		{
			DetailedCommitInfo commitInfo = commitsProvider.Get(rev);
			var calculator = createCalculator.Create(commitInfo);
			changesProcessor.ProcessChanges(
				rev,
				new ProcessChangesParams
					{
						OnNextFile = calculator.OnNextFile,
						OnNextChunk = calculator.OnNextChunk,
						OnNextChange = calculator.OnNextChange
					});
			return new MetricValue(rev, "InternalRate", calculator.GetMetricValue());
		}


		public override IAggregator CreateAggregator()
		{
			return new SumPerDay();
		}
	}

	[Pluggable(Reuse = ReusePolicy.Never)]
	public class InternalRateMetricClaculator
	{
		private readonly IEnumerable<FileChangeInfo> changedFiles;
		private readonly IFilesFilter filesFilter;
		private readonly LinesModificationCounter modCounter = new LinesModificationCounter();

		[ContainerConstructor]
		public InternalRateMetricClaculator(DetailedCommitInfo commitInfo, IFilesFilter filesFilter)
			: this(commitInfo.Changes, filesFilter)
		{
		}

		public InternalRateMetricClaculator(IEnumerable<FileChangeInfo> changedFiles, IFilesFilter filesFilter)
		{
			this.changedFiles = changedFiles;
			this.filesFilter = filesFilter;
		}

		public bool OnNextFile(string filename)
		{
			if(!IsProcessableFile(filename)) return false;
			modCounter.SetFilename(filename);
			return true;
		}

		private bool IsProcessableFile(string filename)
		{
			return filesFilter.IsAnalysableSourceFile(filename, changedFiles);
		}

		public void OnNextChunk()
		{
			modCounter.FinishChunk();
		}

		public void OnNextChange(LineChangeInfo lineChange)
		{
			if(lineChange is LineRemovedInfo) modCounter.Removed(lineChange.Line);
			if(lineChange is LineAddedInfo) modCounter.Added(lineChange.Line);
		}

		public double GetMetricValue()
		{
			modCounter.FinishChunk();
			return modCounter.LinesChanged;
		}
	}

	public class FilesFilter : IFilesFilter
	{
		private readonly ISourcesSettings sourcesSettings;
		private readonly string repoUrl;

		[ContainerConstructor]
		public FilesFilter(ISourcesSettings sourcesSettings, IVcsRepo repo)
			: this(sourcesSettings, repo.Url)
		{
		}

		public FilesFilter(ISourcesSettings sourcesSettings, string repoUrl)
		{
			this.sourcesSettings = sourcesSettings;
			this.repoUrl = repoUrl;
		}

		public bool IsAnalysableSourceFile(string filenameFromDiff, IEnumerable<FileChangeInfo> filesFromLog)
		{
			return sourcesSettings.IsSourceFile(filenameFromDiff) && !FileIsACopyFromOtherBranch(filenameFromDiff, filesFromLog, repoUrl);
		}

		public static bool FileIsACopyFromOtherBranch(string filenameFromDiff, IEnumerable<FileChangeInfo> filesFromLog, string repoUrl)
		{
			return filesFromLog.Any(file => IsACopy(file, filenameFromDiff, repoUrl));
		}

		private static bool IsACopy(FileChangeInfo fileFromLog, string filenameFromDiff, string repoUrl)
		{
			var add = fileFromLog as FileAddedInfo;
			if(add == null) return false;
			if(string.IsNullOrEmpty(add.CopyFromPath)) return false;
			// cuttedPath - это fileFromLog.Path без своего максимального префикса, 
			// являющийся суффиксом repoUrl. 
			// Если filenameFromDiff лежит внутри (или совпадает с) path, 
			// то cuttedPath будет префиксом filenameFromDiff
			string cuttedPath = fileFromLog.Path.CutOffMaxPrefixWhichIsSuffixOf(repoUrl);
			if(cuttedPath.StartsWith("/")) cuttedPath = cuttedPath.Substring(1);
			return filenameFromDiff.StartsWith(cuttedPath);
		}
	}
	[DontUsePluggable(typeof(NullFilesFilter))]
	public interface IFilesFilter
	{
		bool IsAnalysableSourceFile(string filenameFromDiff, IEnumerable<FileChangeInfo> filesFromLog);
	}

	[IgnoredPluggable]
	public class NullFilesFilter : IFilesFilter
	{
		public bool IsAnalysableSourceFile(string filenameFromDiff, IEnumerable<FileChangeInfo> filesFromLog)
		{
			return true;
		}
	}
}